package com.kandinfo.survey.weixin.service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.kandinfo.survey.weixin.mongo.WxConfigure;
import com.tencent.common.Configure;
import com.tencent.common.Util;


/**
 * Description: 获取微信用户信息<p>
 * access_token是公众号的全局唯一票据，公众号调用各接口时都需使用access_token。
 * 正常情况下access_token有效期为7200秒，重复获取将导致上次获取的access_token失效。
 * 由于获取access_token的api调用次数非常有限，建议开发者全局存储与更新access_token，
 * 频繁刷新access_token会导致api调用受限，影响自身业务, 因此将token保存到mongo中
 * 
 * @author Patrick Chenzp-TG
 * @version 1.0
 * @createDate 2016-08-22
 */
@Service
public class WxMongoAccessTokenService {

	public static final Map<String, WxConfigure> wxConfigureMap = new HashMap<String, WxConfigure>();

	@Autowired
	IWxMongoRelativeService mongoRelativeService;
	
	private static final Log logger = LogFactory.getLog(WxMongoAccessTokenService.class);
	
	/**
	 * 获取token，第一步检查内存是否有可用token提升效率，如果没有才去数据库查并且把查询的可用结果存储到内存，如果查询的也没有则新建
	 * @param appId 公众号appid
	 * @param url 回调函数url
	 * @param state 回调函数传回参数
	 * @param scope 链接授权作用域
	 */
	public synchronized WxConfigure getWxConfigure(String appId, String appSecret) {
		if (isTokenNew(appId)) {
			initWxConfigure(appId, appSecret);
		}
		if (isTokenTimeout(appId) || isTicketTimeout(appId)) {
			refreshAccessToken(appId, appSecret);// 更新token
			refreshJsapiTicket(appId, getConfigure(appId).getAccessToken());//更新ticket
		}
		mongoRelativeService.save(getConfigure(appId));// 更新到mongo
		return getConfigure(appId);
	}
	
	public synchronized WxConfigure resetAccessToken(String appId, String appSecret) {
		initWxConfigure(appId, appSecret);
		refreshAccessToken(appId, appSecret);// 更新token
		refreshJsapiTicket(appId, getConfigure(appId).getAccessToken());//更新ticket
		mongoRelativeService.save(getConfigure(appId));// 更新到mongo
		return getConfigure(appId);
	}
	
	private WxConfigure getConfigure(String appId) {
		return wxConfigureMap.get(appId);
	}
	
	private boolean isTokenNew(String appId) {
		return getConfigure(appId) == null;
	}
	
	private boolean isTokenTimeout(String appId) {
		WxConfigure configure = getConfigure(appId);
		long currentTime = System.currentTimeMillis();
		long lastGetTime = configure == null || configure.getTokenUpdateTime() == null ? 0 : configure.getTokenUpdateTime();
		return (currentTime - lastGetTime) >= (configure.getTokenTimeout()|Configure.TOKEN_TIMEOUT * 1000);
	}
	
	private boolean isTicketTimeout(String appId) {
		WxConfigure configure = getConfigure(appId);
		long currentTime = System.currentTimeMillis();
		long lastGetTime = configure == null || configure.getTicketUpdateTime() == null ? 0 : configure.getTicketUpdateTime();
		return (currentTime - lastGetTime) >= (configure.getTicketTimeout()|Configure.TICKET_TIMEOUT * 1000);
	}
	
	/**
	 * 刷新
	 * @param appId
	 * @param appsecret
	 * @return
	 */
	private synchronized void refreshAccessToken(String appId, String appSecret) {
		try {
			String url = Configure.TOKEN_API.replace("APPID", Util.urlEnodeUTF8(appId)).replace("APPSECRET", Util.urlEnodeUTF8(appSecret));
			HttpGet request = new HttpGet(url);
			HttpResponse response = HttpClients.createDefault().execute(request);
			
			if (response.getStatusLine().getStatusCode() == 200) {
				String result = EntityUtils.toString(response.getEntity());
				JSONObject jsonObject = new JSONObject(result);
				if (jsonObject.get("access_token")  != null && jsonObject.get("expires_in") != null) {
					getConfigure(appId).setAppSecret(appSecret);
					getConfigure(appId).setAccessToken(jsonObject.get("access_token").toString());
					getConfigure(appId).setTokenUpdateTime(System.currentTimeMillis());
					getConfigure(appId).setTokenTimeout(Integer.valueOf(jsonObject.get("expires_in").toString()));
					getConfigure(appId).setUpdateTime(new Date());
				}
			}
			
			// for test
//			WxConfigure configure = initWxConfigure(appId, appSecret);
//			domain.setId(Configure.getAppid());
//			domain.setAppSecret(Configure.getAppSecret());
//			domain.setAccessToken("123333");
//			domain.setTokenTimeout(System.currentTimeMillis());
//			mongoRelativeService.save(domain);
		} catch (Exception e) {
			logger.error("获取access_token失败", e);
		}
	}

	/**
	 * 刷新
	 * @param appid
	 * @param accessToken
	 * @return
	 */
	private synchronized void refreshJsapiTicket(String appId, String accessToken) {
		try {
			String url = Configure.TICKET_API.replace("ACCESS_TOKEN", accessToken);
			HttpGet request = new HttpGet(url);
			HttpResponse response = HttpClients.createDefault().execute(request);
			if (response.getStatusLine().getStatusCode() == 200) {
				String result = EntityUtils.toString(response.getEntity());
				JSONObject jsonObject = new JSONObject(result);
				if (jsonObject.get("ticket")  != null && jsonObject.get("expires_in") != null) {
					getConfigure(appId).setJsapiTicket(jsonObject.get("ticket").toString());
					getConfigure(appId).setTicketUpdateTime(System.currentTimeMillis());
					getConfigure(appId).setTicketTimeout(Integer.valueOf(jsonObject.get("expires_in").toString()));
					getConfigure(appId).setUpdateTime(new Date());
				}
			}
		} catch (Exception e) {
			logger.error("获取jsapi_ticket失败", e);
		}
	}

	
	/**
	 * 初始化
	 * @param appId
	 * @param appSecret
	 * @return
	 */
	public WxConfigure initWxConfigure(String appId, String appSecret) {
		WxConfigure configure = wxConfigureMap.get(appId);
		if (configure == null) {
			configure = mongoRelativeService.findOne(appId);
		}
		if (configure == null) {
			configure = new WxConfigure();
			configure.setAppId(appId);
			configure.setAppSecret(appSecret);
			
			configure.setTokenTimeout(Configure.TOKEN_TIMEOUT);
			configure.setTokenUpdateTime(0l);
			
			configure.setTicketTimeout(Configure.TICKET_TIMEOUT);
			configure.setTicketUpdateTime(0l);
			
			configure.setUpdateTime(new Date());
		}
		wxConfigureMap.put(appId, configure);
		return configure;
	}

}
